package com.happy3w.ssp.schoolmatehome.service.note;

import com.happy3w.ssp.schoolmatehome.entity.Note;
import com.happy3w.ssp.schoolmatehome.entity.NoteTag;
import com.happy3w.ssp.schoolmatehome.entity.User;
import com.happy3w.ssp.schoolmatehome.exception.MessageException;
import com.happy3w.ssp.schoolmatehome.repository.FollowRepository;
import com.happy3w.ssp.schoolmatehome.repository.NoteRepository;
import com.happy3w.ssp.schoolmatehome.repository.NoteStateRepository;
import com.happy3w.ssp.schoolmatehome.repository.NoteTagRepository;
import com.happy3w.ssp.schoolmatehome.service.user.UserService;
import com.happy3w.ssp.schoolmatehome.service.user.UserStateSummary;
import com.happy3w.ssp.schoolmatehome.service.user.UserSummary;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;
import java.util.Optional;

@RequiredArgsConstructor
@Service
public class NoteService {
    private final UserService userService;
    private final NoteRepository noteRepository;
    private final NoteTagRepository noteTagRepository;
    private final NoteStateRepository noteStateRepository;
    private final FollowRepository followRepository;

    public Optional<NoteDetail> findById(Long id, User user) {
        return noteRepository.findById(id)
                .filter(note -> Note.SCOPE_PUBLIC.equals(note.getScope()) || note.getOwnerId().equals(user.getId()))
                .map(note -> {
                    List<NoteTag> tags = noteTagRepository.findAllByNoteId(note.getId());
                    long ownerId = note.getOwnerId();
                    User owner = user.getId() == ownerId
                            ? user
                            : userService.getUserById(ownerId).orElseThrow(() -> new MessageException("user not found"));
                    return NoteDetail.of(note, owner, tags);
                });
    }

    @Transactional
    public NoteDetail createNote(NoteDetail noteDetail, User user) {
        Note note = noteDetail.buildNote();
        note.setOwnerId(user.getId());
        note.setCreatedAt(new Date());
        note.setUpdatedAt(new Date());
        Note newNote = noteRepository.save(note);
        List<NoteTag> newTags = noteTagRepository.persistAll(noteDetail.getTags(), newNote.getId());
        noteStateRepository.initState(note.getId());
        return NoteDetail.of(newNote, user, newTags);
    }

    @Transactional
    public NoteDetail updateNote(Long id, NoteDetail noteDetail, User user) {
        Optional<Note> existNoteOpt = noteRepository.findById(id);
        if (existNoteOpt.isEmpty()) {
            return null;
        }
        Note existNote = existNoteOpt.get();
        if (!existNote.getOwnerId().equals(user.getId())) {
            return null;
        }
        existNote.copyValues(noteDetail.buildNote());
        existNote.setUpdatedAt(new Date());
        noteRepository.save(existNote);

        List<NoteTag> newTags = noteTagRepository.persistAll(noteDetail.getTags(), existNote.getId());
        return NoteDetail.of(existNote, user, newTags);
    }

    @Transactional
    public void deleteById(Long id, User user) {
        Optional<Note> noteOpt = noteRepository.findById(id);
        if (noteOpt.isEmpty()) {
            return;
        }
        Note note = noteOpt.get();
        if (!note.getOwnerId().equals(user.getId())) {
            return;
        }
        noteRepository.deleteById(id);
        noteTagRepository.deleteByNoteId(id);
        noteStateRepository.deleteByNoteId(id);
    }

    public Optional<NoteReadDto> readNote(Long id, User reader) {
        return noteRepository.findById(id)
                .map(note -> {
                    User owner = userService.getUserById(note.getOwnerId()).orElseThrow(() -> new MessageException("owner not found"));
                    if (!note.getOwnerId().equals(reader.getId()) && !note.getScope().equals(Note.SCOPE_PUBLIC)) {
                        return null;
                    }
                    List<NoteTag> tags = noteTagRepository.findAllByNoteId(note.getId());
                    noteStateRepository.markRead(note.getId(), reader.getId());
                    NoteStateSummary stateSummary;
                    try {
                        stateSummary = noteStateRepository.findById(note.getId(), reader.getId());
                    } catch (EmptyResultDataAccessException e) {
                        stateSummary = NoteStateSummary.empty();
                        noteStateRepository.initState(note.getId());
                    }

                    return NoteReadDto.builder()
                            .note(NoteDetail.of(note, owner, tags))
                            .owner(UserSummary.builder()
                                    .id(owner.getId())
                                    .name(owner.getName())
                                    .avatar(owner.getAvatar())
                                    .isFollowed(followRepository.isFollowed(owner.getId(), reader.getId()))
                                    .build()
                            )
                            .state(UserStateSummary.builder()
                                    .viewCount(stateSummary.getViewCount())
                                    .favoriteCount(stateSummary.getFavoriteCount())
                                    .collectCount(stateSummary.getCollectCount())
                                    .build())
                            .isFavorite(Boolean.TRUE.equals(stateSummary.getIsFavorite()))
                            .isCollect(Boolean.TRUE.equals(stateSummary.getIsCollect()))
                            .isTemplate(Boolean.TRUE.equals(stateSummary.getIsTemplate()))
                            .build();

                });
    }

    public void markFavorite(Long id, User user) {
        noteStateRepository.markFavorite(id, user.getId());
    }

    public void unmarkFavorite(Long id, User user) {
        noteStateRepository.unmarkFavorite(id, user.getId());
    }

    public void markCollect(Long id, User user) {
        noteStateRepository.markCollect(id, user.getId());
    }

    public void unmarkCollect(Long id, User user) {
        noteStateRepository.unmarkCollect(id, user.getId());
    }

    public void markTemplate(Long id, User user) {
        noteStateRepository.markTemplate(user.getId(), id);
    }

    public void unmarkTemplate(Long id, User user) {
        noteStateRepository.unmarkTemplate(user.getId(), id);
    }
}